01 context
任何方法,谁调用了它,则就是它的 context。
这个和 this 的描述是一致的:
this 最终指向的是调用它的对象
上下文就是 this 的指向。
02 不稳定的 context
context 在 js 的表现经常出现一些看似”不稳定”的情况,下面列出了一些”不稳定”。
(1) new 和非 new
下面是一道常见的面试题,问 ① 和 ② 分别输出什么结果?
function say() {
console.log(this)
}
new say() // ①
say() // ②
虽然简单,却是对 context 最全的理解。 ① 输出 say 的实例 ②输出 window 吗? 显然我们凭借这段代码是无法做出对 ② 的判断,因为我们根本不知道 say()的运行环境是什么。
(2) 方法赋值
下面试题,将方法赋值给一个变量。
var name = "嗷嗷嗷"
var cat = {
name: "喵",
say: function () {
console.log(this.name)
},
}
var say = cat.say
cat.say() // ①
say() // ②
结果: ① 输出 喵 ② 输出 嗷嗷嗷
这里可以理解:因为 say 其实只是引用了一个函数,函数的运行依赖 context,不同的 context 必然有不同结果。
(3) Event Loop
Event Loop 又叫事件循环,由于 js 没有多线程,处理多个任务的时候,就需要采用异步队列(如定时器、I/O、Promse),或者利用浏览器的多线程启动多个任务(如 ajax)。
那么异步任务,它的 context 就是浏览器当前打开的窗口了,即 window。
下面是一个常见的面试问题:
function say() {
setTimeout(function () {
console.log(this)
}, 0)
}
new say() //①
say() //②
① 和 ② 都输出 window。
(4) 自执行函数
看下面一个问题:
function say() {
;(function () {
console.log(this)
})()
}
new say() //①
say() //②
① 和 ② 都输出 window,这里也说明了自执行函数是被 window 调用。
03 闭包拯救世界
面对不稳定的 context,闭包可以被动解决这些问题。 因为我们所期望的 context,和闭包所表现的 scope 惊人相似。
于是我们纷纷这样做:
function say() {
var _this = this
setTimeout(function () {
console.log(_this)
}, 0)
}
new say() //①
say() //②
function say() {
;(function (_this) {
console.log(_this)
})(this)
}
new say() //①
say() //②
上面做法用_this 代替 this,使得正常访问外层 this。
04 bind 来优化
对于用闭包来改变 context 的问题,不是很优雅。 用 bind 会更简洁。
实现是下面代码:
function say() {
setTimeout(
function () {
console.log(this)
}.bind(this),
0
)
}
new say() //①
say() //②
通过 bind 直接改变当前函数的 context,这样子做法是符合我们的阅读习惯的。
05 call 和 apply
call 和 apply 又叫对象冒充,是在方法执行的时候,传入一个对象,顶替原有的 context。
下面代码通过 call,主动改变的执行函数的 context。这在封装里面很重要。
<div onclick="clickdiv.call(this)">click me</div>
<script type="text/javascript">
function clickdiv() {
console.log(this)
}
</script>
点击 div,打印 div 元素。
06 箭头函数
可能意识到 context 这个问题,于是 es6 推出箭头函数。箭头函数采用词法作用域,这使得它和闭包的解决极为相似。
注意:arguments 本身在箭头函数下,会指向外层函数的 arguments (同 this 一样)。
function say() {
setTimeout(() => {
console.log(this)
}, 0)
}
new say() //①
say() //②
这里 ① 和 ② 都正常输出了。
07 Arguments
Arguments 也是 context 绕不过去的坎,Arguments 是函数的内部对象,js 在执行函数的时候,会根据 Arguments 初始化函数内部的变量。
arguments.callee 可以访问到当前函数。 arguments.callee.caller 可以访问调用当前函数的函数。
arguments 上面特性严格模式下是不允许被使用的。